package org.biogroovy.io

import groovy.util.logging.Slf4j
import groovy.util.slurpersupport.GPathResult
import groovy.util.slurpersupport.NodeChild
import groovy.util.slurpersupport.NodeChildren

import java.text.SimpleDateFormat

/**
 * This implementation of the IReader interface uses the XmlSlurper 
 * to parse XML documents.  This implementation is faster and should
 * provide performance improvements over the DOM-based parser.
 *
 * @param <P>  the POJO type
 */
@Slf4j
abstract class AbsXmlSlurper<P> extends AbsFetcher<P> implements ISlurper<P>{

	protected SimpleDateFormat dateFormatter = new SimpleDateFormat("YYYY-MM-dd");
	

    /**
     * This method reads an input stream containing the XML for a protein or transcript.
     * The XML is assumed to be the same format as that returned from the EUtils
     * RESTful web service.
     * @param inputStream  The input stream containing the XML record.
     */
    public abstract P read(InputStream inputStream) throws IOException;


    /**
     * This method reads a file containing the XML for an object.
     * @param file  The file containing the XML record.
     */
    public P read(File file) throws IOException{
		return read(new FileInputStream(file));
	}


    /**
     * This method reads an input stream containing the XML for a list of objects.
     */
    public abstract List<P> readList(InputStream inputStream) throws IOException;


    /**
     * This method reads a file containing the XML for a list of objects.
     * @param file The file containing the list o XML records.
     */
    public List<P> readList(File file) throws IOException{
        return readList(new FileInputStream(file));
    }





    /**
     * This method parses elements defined in the xpathMap from the rootNode.
     * @param rootNode	The root node of the document.
     * @param model		The model object being that will be filled. (i.e. a Gene object).
     * @param gpathMap	A map whose keys are the names of the model properties, and whose values are 
     * 					the XPath's required to parse out these objects.
     * @param nodeTypeMap
     * 					A map whose keys are the names of the model properties and whose values are
     * 					NodeType constants that indicate the type of node to parsed by the XPath expression
     */
    protected void parseData(NodeChild rootNode, P model, Map<String, String> gpathMap, Map<String, NodeType> nodeTypeMap){
        Map propertiesMap = model.getProperties(); 
        def nuPropMap = new HashMap<String, Object>();

        propertiesMap.each(){ key,value ->
            if (gpathMap.containsKey(key) && nodeTypeMap.containsKey(key)){
                def currval = null;
                if (nodeTypeMap.get(key).equals(NodeType.STRING)){
                    currval = parseString(rootNode, gpathMap.get(key));
                }else if (nodeTypeMap.get(key).equals(NodeType.INTEGER)){
                    currval = parseInteger(rootNode, gpathMap.get(key))
                }else if (nodeTypeMap.get(key).equals(NodeType.DOUBLE)){
					currval = parseDouble(rootNode, gpathMap.get(key))
				}else if (nodeTypeMap.get(key).equals(NodeType.LONG)){
					currval = parseLong(rootNode, gpathMap.get(key))
                }else if (nodeTypeMap.get(key).equals(NodeType.LIST)){
                    currval = parseList(rootNode, gpathMap.get(key))  
                }
				model.setProperty(key, currval)
                log.debug("${key}:${currval}")
            }
        }
		
    }
	
	abstract protected void parseDbReferences(NodeChild root, P node);

    /**
     * This method parses a single string value using an xpath expression.
     * @param rootNode  The root node of the document.
     * @param gPathStr  The xpath expression.
     */
    String parseString(NodeChild rootNode, String gPathStr){
        return Eval.x( rootNode, "x.$gPathStr" );
    }


	
	/**
	 * This method parses a single numeric value using an gPath expression.
	 * @param rootNode  The root node of the document.
	 * @param gPathStr  The gPath expression.
	 * @return the integer value
	 */
	Integer parseInteger(NodeChild rootNode, String gPathStr){
		Integer num = null;
		try{
			num = Eval.x(rootNode,"x.${gPathStr}").toInteger();
		}catch (NumberFormatException nfe) {
			log.error ("Error -- NumberFormatException")
			// eat the exception, return null and carry on
		}catch (Exception ex){
			log.error( "Error -- XPath = ${gPathStr}");
			throw ex;
		}
		return num
	}

	/**
	 * This method parses a single numeric value using a gPath expression.
	 * @param rootNode  The root node of the document.
	 * @param gPathStr  The gPath expression.
	 * @return the long value
	 */
	Long parseLong(NodeChild rootNode, String gPathStr){
		Long num = null;
		try{
			num = Eval.x(rootNode,"x.${gPathStr}").toLong();
		}catch (Exception ex){
			log.error("Error -- XPath = ${gPathStr}");
			throw ex;
		}
		return num
	}
	
	/**
	 * This method parses a single numeric value using a gPath expression
	 * @param rootNode  The root node of the document.
	 * @param gPathStr  The gPath expression.
	 * @return the double value
	 */
	Double parseDouble(NodeChild rootNode, String xpathStr){
		Double num = null;
		try{
			num = Eval.x(rootNode,"x.${xpathStr}").toDouble();
		}catch (Exception ex){
			log.error("Error -- XPath = ${xpathStr}");
			throw ex;
		}
		return num
	}


	


    /**
     * This method parses a list of String values using an xpath expression.
     * @param rootNode  The root node of the document.
     * @param gPathStr  The gPath expression.
     */
    List<String> parseList(GPathResult rootNode, String gPathStr){
		List<String> results = new ArrayList<>();
		
		def result = Eval.x(rootNode,"x.${gPathStr}");
		if(result instanceof ArrayList) {
			results = result;
		}else if (result instanceof NodeChildren) {
			result.each{GPathResult currNode ->
				results.add(currNode.text())
			}
		}
		
		return results
    }



	
	

	/**
	 * Creates and configures an XML slurper.
	 * @return
	 */
	protected XmlSlurper createSlurper() {
		XmlSlurper slurper = new XmlSlurper(false, false);
		slurper.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false);
		slurper.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
		slurper.setFeature("http://xml.org/sax/features/namespaces", false)
		return slurper
	}

}
